# -*- coding: utf-8 -*-
"""
Author: chen xi
Date: 2025/4/30 上午11:26
File: system_actions.py
用途：更加底层实现每个操作
"""
import json
import os
import time

from src.com.config_utils import read_config, find_directory
from uiautomator2_driver import AndroidAppDriver
from src.com.logging_utils import Logger
from ui_hierarchy_utils import get_level_differences


def _get_target_attributes(raw_data, supported_attributes):
	"""
	获取目标元素的属性映射。
	"""
	return {attr: raw_data.get(attr) for attr in supported_attributes if raw_data.get(attr)}


class SystemAction(AndroidAppDriver):
	
	def __init__(self, android_device_id=None, app_package_name=None):
		super().__init__(android_device_id=android_device_id, app_package_name=app_package_name)
		# 项目根目录
		self.project_root = os.path.dirname(find_directory("config"))
		# 获取app相关配置参数
		self.app_config = read_config(os.path.join(find_directory("config"), "app_config.ini"))
	
	def find_element(self, _step: dict):
		"""
		根据提供的定位信息字典定位元素。

		参数:
		_step (dict): 包含定位信息的字典，键为定位方法（如'text', 'id', 'bounds'），值为对应的定位值。

		返回:
		_selector.UiObject: 定位到的元素对象。

		注意：该方法会遍历预定义的定位属性列表，找到第一个在 raw_data 字典中不为 None 的定位属性，并使用它定位元素。
		"""
		# 提取原始定位数据
		raw_data: dict = _step.get("raw")
		
		# 获取支持的定位属性列表
		supported_attributes: list = json.loads(self.app_config.get("Tables", "ATTRIBUTE_LIST"))
		
		# 初始化属性映射字典，所有值初始化为 None
		attribute_mapping: dict = dict.fromkeys(supported_attributes, None)
		
		# 获取当前页面的层级结构
		page_source: str = self.driver.dump_hierarchy()
		
		# 遍历支持的定位属性
		for attribute in attribute_mapping.keys():
			# 检查值是否为空或 None
			if not raw_data.get(attribute):
				continue
			
			# 检查当前页面是否存在该值
			if raw_data.get(attribute) in page_source:
				attribute_mapping[attribute] = raw_data.get(attribute)
		
		Logger.info(f"Attribute Mapping: {attribute_mapping}")
		
		# 使用属性映射字典定位元素
		return self.find_element_by_attributes(attribute_mapping)
	
	def find_element_by_attributes(self, attributes):
		"""
		根据属性字典定位元素，并替换无效键名为有效键名。

		:param attributes: 包含元素属性的字典
		:return: 元素对象或 None
		"""
		# 定义键名映射
		KEY_MAPPING = {
			'resource-id': 'resourceId',  # 替换 resource-id 为 resourceId
			'class': 'className',  # 如果有类似的其他无效键名，也可以在这里定义
		}
		
		# 替换无效键名为有效键名
		corrected_attributes = {KEY_MAPPING.get(k, k): v for k, v in attributes.items() if v is not None}
		
		# 动态构造选择器
		selector = self.driver(**corrected_attributes)
		return selector if selector.exists else None
	
	def find_element_by_swiping(
			self,
			_step: dict,
			anchor_text: str = None,
			direction: str = "up",
			max_attempts: int = 10
	):
		"""
		通过滑动屏幕定位元素，并支持通过锚点或目标元素数据进行滑动判断。
		"""
		raw_data = _step.get("raw")
		if not raw_data:
			Logger.warning("Raw data is missing in step, cannot proceed.")
			return None
		
		supported_attributes = json.loads(self.app_config.get("Tables", "ATTRIBUTE_LIST"))
		initial_page_source = self.driver.dump_hierarchy()
		last_page_source = initial_page_source
		
		for attempt in range(max_attempts):
			Logger.info(f"Attempt {attempt + 1} of {max_attempts}")
			
			page_source = self.driver.dump_hierarchy()
			# print(anchor_text, self._try_find_with_anchor(anchor_text, raw_data, direction))
			# 如果有锚点，则优先使用锚点查找目标元素
			if anchor_text and self._try_find_with_anchor(anchor_text, raw_data, direction) is not None:
				index = self._try_find_with_anchor(anchor_text, raw_data, direction)
				return self.find_element_by_attributes(self._get_target_attributes(raw_data, supported_attributes))[
					index]
			
			# 直接查找目标元素
			elif anchor_text is None and self._try_find_directly(page_source, raw_data, supported_attributes):
				return self.find_element_by_attributes(self._get_target_attributes(raw_data, supported_attributes))
			
			# 滑动屏幕
			self.swipe_screen(direction)
			
			# 判断是否回到初始状态（页面无变化）
			if self._is_end_of_scroll(last_page_source, page_source, attempt):
				break
			
			last_page_source = page_source
		
		Logger.error("Element not found after maximum attempts.")
		return None
	
	def _try_find_with_anchor(self, anchor_text, raw_data, direction):
		"""
		使用锚点尝试找到目标元素。
		"""
		element1_text = raw_data.get("text") or raw_data.get("resource-id")
		element2_text = anchor_text
		count = 3
		while count > 0:
			index = get_level_differences(self.driver, element1_text, element2_text)
			if index is not None:
				return index
			self.swipe_screen(direction)
			count -= 1
		return None
	
	@staticmethod
	def _try_find_directly(page_source, raw_data, supported_attributes):
		"""
		直接尝试找到目标元素。
		"""
		for attr in supported_attributes:
			value = raw_data.get(attr)
			if value and value in page_source:
				return True
		return False
	
	@staticmethod
	def _get_target_attributes(raw_data, supported_attributes):
		"""
		获取目标元素的属性映射。
		"""
		return {attr: raw_data.get(attr) for attr in supported_attributes if raw_data.get(attr)}
	
	@staticmethod
	def _is_end_of_scroll(last_page_source, current_page_source, attempt):
		"""
		判断是否已经到达滚动终点。
		"""
		if current_page_source == last_page_source and attempt > 0:
			Logger.warning("Page source unchanged. Screen might be at the end.")
			return True
		return False
	
	def swipe_screen(self, direction: str = "up", distance_ratio: float = 0.25):
		"""
		滑动屏幕。

		参数:
		direction (str): 滑动方向，默认为 "up"（向上）。可选值："up", "down", "_left", "_right"。
		distance_ratio (float): 滑动距离与屏幕长度或宽度的比例，默认为0.5。取值范围0到1之间。
		"""
		if not 0 <= distance_ratio <= 1:
			raise ValueError("Distance ratio should be between 0 and 1.")
		
		# 获取屏幕尺寸
		window_size = self.driver.window_size()
		width = window_size[0]
		height = window_size[1]
		
		# 根据方向和距离比例计算滑动的起始和结束坐标
		if direction == "down":
			start_x, start_y = width * 2 // 3, int(height * (0.5 - distance_ratio / 2))
			end_x, end_y = width * 2 // 3, int(height * (0.5 + distance_ratio / 2))
		elif direction == "up":
			start_x, start_y = width * 2 // 3, int(height * (0.5 + distance_ratio / 2))
			end_x, end_y = width * 2 // 3, int(height * (0.5 - distance_ratio / 2))
		elif direction == "_left":
			start_x, start_y = int(width * (0.5 + distance_ratio / 2)), height // 2
			end_x, end_y = int(width * (0.5 - distance_ratio / 2)), height // 2
		elif direction == "_right":
			start_x, start_y = int(width * (0.5 - distance_ratio / 2)), height // 2
			end_x, end_y = int(width * (0.5 + distance_ratio / 2)), height // 2
		else:
			raise ValueError(f"Invalid direction: {direction}. Supported values are 'up', 'down', '_left', '_right'.")
		
		# 执行滑动操作
		self.driver.swipe(start_x, start_y, end_x, end_y)
		Logger.info(f"Swiped screen in direction: {direction} with distance ratio: {distance_ratio}")
	
	def is_element_exists(self, _step: dict):
		"""
		判断指定的UI元素是否存在。
		:param selector: 元素的选择器，可以是文本、资源ID等
		:return: 如果元素存在返回True，否则返回False
		"""
		try:
			if _step.get("raw").get("text"):
				_element = self.driver(text=_step.get("raw").get("text"))
				return _element.exists(timeout=1)  # 设置超时时间为1秒
			else:
				_element = self.driver(resourceId=_step.get("raw").get("resource-id"))
				return _element.exists(timeout=1)  # 设置超时时间为1秒
		except Exception as e:
			# print(f"发生异常: {e}")
			return False
	
	def click_by_coordinates(self, coordinates_str):
		"""
		基于坐标的点击方法，点击矩形区域的中心点。

		:param coordinates_str: 坐标字符串，格式为 "[x1,y1][x2,y2]"
		"""
		try:
			# 解析坐标字符串
			coords = coordinates_str.strip('[]').split('][')
			x1, y1 = map(int, coords[0].split(','))
			x2, y2 = map(int, coords[1].split(','))
			
			# 计算中心点
			center_x = (x1 + x2) // 2
			center_y = (y1 + y2) // 2
			
			# 点击中心点
			self.driver.click(center_x, center_y)
			Logger.info(f"点击坐标 ({center_x}, {center_y}) 成功")
		except Exception as e:
			Logger.info(f"点击坐标失败: {e}")
			raise


if __name__ == "__main__":
	step = {'页面名称': '首页', '相邻页面': '直播', 'raw': {'NAF': 'true', 'index': '1', 'text': '', 'resource-id': 'com.zwcode.p6slite:id/item_device_play', 'class': 'android.widget.ImageView', 'package': 'com.zwcode.p6slite', 'content-desc': '', 'checkable': 'false', 'checked': 'false', 'clickable': 'true', 'enabled': 'true', 'focusable': 'true', 'focused': 'false', 'scrollable': 'false', 'long-clickable': 'false', 'password': 'false', 'selected': 'false', 'visible-to-user': 'true', 'bounds': '[481,601][598,718]', 'drawing-order': '4', 'hint': '', 'display-id': '0'}, '控件类型': '滑动定位点击', '附加数据': None, '等待时间': None}
	# 示例配置
	ANDROID_DEVICE_ID = "H675FIS8JJU8AMWW"  # 替换为实际设备ID
	APP_PACKAGE_NAME = "com.zwcode.p6slite"  # 替换为实际要启动的 App 包名
	
	# 创建驱动实例并启动 App
	android_app_driver = SystemAction(ANDROID_DEVICE_ID, APP_PACKAGE_NAME)
	time.sleep(15)
	android_app_driver.find_element_by_swiping(_step=step, anchor_text="000299")
	time.sleep(10)
